package com.example.camera2application;

import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Objects;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;

public class CameraFragment extends Fragment {


    private static final String FRAGMENT_DIALOG = "dialog";

    private CameraManager cameraManager;

    private TextView textVIew;

    private WindowManager windowManager;

    private ImageView imageView;

    private ImageView changeCamera;
    private boolean isBack;

    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();

    static {
        ORIENTATIONS.append(Surface.ROTATION_0, 90);
        ORIENTATIONS.append(Surface.ROTATION_90, 0);
        ORIENTATIONS.append(Surface.ROTATION_180, 270);
        ORIENTATIONS.append(Surface.ROTATION_270, 180);
    }



    private final TextureView.SurfaceTextureListener mSurfaceTextureListener =
            new TextureView.SurfaceTextureListener() {
                @Override
                public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                            openCamera();
                }

                @Override
                public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {

                }

                @Override
                public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                    return false;
                }

                @Override
                public void onSurfaceTextureUpdated(SurfaceTexture surface) {

                }
            };
    private String mCameraId;

    private TextureView mTextureView;

    private CameraCaptureSession mCaptureSession;

    private CameraDevice mCameraDevice;


    private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            mCameraOpenCloseLock.release();
            mCameraDevice = camera;
            ///創建預覽
            createCameraPreviewSession();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
          mCameraOpenCloseLock.release();
            camera.close();
            mCameraDevice = null;
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            mCameraOpenCloseLock.release();
            camera.close();
            mCameraDevice = null;
            Activity activity = getActivity();
            if(null != activity){
                activity.finish();
            }
        }
    };


    private HandlerThread mBackgroundThread;

    private Handler mBackgroundHandler;

    private ImageReader mImageReader;

    private final ImageReader.OnImageAvailableListener mOnImageAvailableListener =
            new ImageReader.OnImageAvailableListener() {
                @Override
                public void onImageAvailable( ImageReader reader) {
                   // mBackgroundHandler.post(new ImageSaver(reader.acquireNextImage(),mFile));
              /*      try {
                        semaphore.acquire();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }*/
                    Log.d("image",Thread.currentThread().getName());
                    Image image = reader.acquireNextImage();
                    ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                    byte[] bytes = new byte[buffer.remaining()];
                    buffer.get(bytes);
                    final Bitmap bitmap = BitmapFactory.decodeByteArray(
                            bytes,0,bytes.length);
                    image.close();
//                    semaphore.release();
                    Matrix matrix = new Matrix();
                    matrix.postRotate(90);
                    final  Bitmap rotatedBitmap = Bitmap.createBitmap(
                            bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true);
                    if(rotatedBitmap != null){
                        Objects.requireNonNull(getActivity()).runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                imageView.setImageBitmap(rotatedBitmap);

                            }
                        });

                    }
                    saveImage(rotatedBitmap);


                }
            };

    private CaptureRequest.Builder mPreviewRequestBuilder;

    private CaptureRequest mPreviewRequest;

    private Semaphore mCameraOpenCloseLock = new Semaphore(1);

    private boolean mFlashSupported;

    private int mSensorOrientation;

    private CameraCaptureSession.CaptureCallback mCaptureCallback =
            new CameraCaptureSession.CaptureCallback() {
                @Override
                public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) {
                    super.onCaptureProgressed(session, request, partialResult);
                }
            };


    public static CameraFragment newInstance(){
        return new CameraFragment();
    }


    private int getOrientation(int rotation) {
        // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X)
        // We have to take that into account and rotate JPEG properly.
        // For devices with orientation of 90, we simply return our mapping from ORIENTATIONS.
        // For devices with orientation of 270, we need to rotate the JPEG 180 degrees.
        return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360;
    }



    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        cameraManager = (CameraManager) Objects.requireNonNull(getActivity()).getSystemService(Context.CAMERA_SERVICE);
        windowManager = (WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE);
        selectCameraId();

    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_camerainfo,container,false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        isBack  = true;
       // textVIew = view.findViewById(R.id.camera_info);
        mTextureView = view.findViewById(R.id.image_show);
        Button button = view.findViewById(R.id.info);
        imageView = view.findViewById(R.id.photo);
        changeCamera = view.findViewById(R.id.camera_info);
        changeCamera.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                isBack = !isBack;
                mCameraOpenCloseLock.release();
                selectCameraId();
               // startBackgroundThread();

                onlyCloseCamera();
                openCamera();
            }
        });
        imageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    String[] cameraIds = cameraManager.getCameraIdList();
                    StringBuilder stringBuilder = new StringBuilder();
                    for(String cameraId:cameraIds){
                        stringBuilder.append(cameraId).append('\n');
                    }
                    Display display = windowManager.getDefaultDisplay();
                    Point point = new Point();
                    display.getSize(point);
                    stringBuilder.append("width:").append(point.x).append('\n');
                    stringBuilder.append("height: ").append(point.y).append('\n');
                    stringBuilder.append("rotation:").append(display.getRotation());
                //    textVIew.setText(stringBuilder.toString());
                    takePicture();
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                }

            }
        });


    }



    private void setAutoFlash(CaptureRequest.Builder requestBuilder){
        if(mFlashSupported){
            requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
                    CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
        }
    }


    private void createCameraPreviewSession(){
        SurfaceTexture texture = mTextureView.getSurfaceTexture();
        assert texture != null;
        texture.setDefaultBufferSize(mTextureView.getWidth(),mTextureView.getHeight());

        Surface surface = new Surface(texture);

        try {
            mPreviewRequestBuilder = mCameraDevice.createCaptureRequest(
                    CameraDevice.TEMPLATE_PREVIEW);
            mPreviewRequestBuilder.addTarget(surface);
            mCameraDevice.createCaptureSession(Arrays.asList(surface,mImageReader.getSurface()),
                    new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    if (mCameraDevice == null){
                        return;
                    }
                     mCaptureSession = session;
                    mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
                            CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                    setAutoFlash(mPreviewRequestBuilder);
                    mPreviewRequest = mPreviewRequestBuilder.build();
                    try {
                        mCaptureSession.setRepeatingRequest(mPreviewRequest,
                                mCaptureCallback,mBackgroundHandler);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession session) {

                }
            },null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }




    private void startPreview(CaptureRequest.Builder builder){
        builder.set(CaptureRequest.CONTROL_AF_MODE,
                CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
        setAutoFlash(builder);
        try {
            mCaptureSession.setRepeatingRequest(builder.build(),mCaptureCallback,mBackgroundHandler);

        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

    }


    private void takePicture(){
        try {
            CaptureRequest.Builder builder = mCameraDevice.createCaptureRequest(
                 CameraDevice.TEMPLATE_STILL_CAPTURE);
            builder.addTarget(mImageReader.getSurface());
            builder.set(CaptureRequest.CONTROL_AF_MODE,
                    CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
            setAutoFlash(builder);
            builder.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_ANTIBANDING_MODE_AUTO);
            int rotation = Objects.requireNonNull(getActivity()).getWindowManager().getDefaultDisplay().getRotation();
            builder.set(CaptureRequest.JPEG_ORIENTATION,getOrientation(rotation) );
            mCaptureSession.stopRepeating();
            //mCaptureSession.abortCaptures();
            mCaptureSession.capture(builder.build(),null,mBackgroundHandler);
           startPreview(mPreviewRequestBuilder);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }


    private void selectCameraId(){
        Activity activity = getActivity();
        assert activity != null;
        CameraManager manager = (CameraManager) activity.getSystemService(
                Context.CAMERA_SERVICE);
        try {

            assert manager != null;
            for(String cameraId:manager.getCameraIdList()){
                CameraCharacteristics cameraCharacteristics =
                        manager.getCameraCharacteristics(cameraId);
                Integer facing = cameraCharacteristics.get(
                        CameraCharacteristics.LENS_FACING);
                if(facing != null) {
                    if (isBack&&facing == CameraCharacteristics.LENS_FACING_BACK) {
                        mCameraId = cameraId;
                        initImageReader(cameraCharacteristics);

                        return;

                    }

                    if(!isBack&&facing == CameraCharacteristics.LENS_FACING_FRONT){
                        mCameraId = cameraId;
                        initImageReader(cameraCharacteristics);

                        return;
                    }


                }


            }
        } catch (CameraAccessException e) {

            ErrorDialog.newInstance("can not open camera")
            .show(getChildFragmentManager(),FRAGMENT_DIALOG);

        }
    }

    private void initImageReader(CameraCharacteristics cameraCharacteristics){
      StreamConfigurationMap  map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
        assert map != null;
        Size largest = Collections.max(
                Arrays.asList(map.getOutputSizes(ImageFormat.JPEG)),
                new CompareSizeByArea());
        mImageReader = ImageReader.newInstance(largest.getWidth(),largest.getHeight(),
                ImageFormat.JPEG,2);
        mImageReader.setOnImageAvailableListener(
                mOnImageAvailableListener,mBackgroundHandler
        );
    }


    @Override
    public void onResume() {
        super.onResume();
        startBackgroundThread();
        if(mCameraId != null){
            try {
                initImageReader(cameraManager.getCameraCharacteristics(mCameraId));
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
        if(mTextureView.isAvailable()){
            openCamera();
        }else {
            mTextureView.setSurfaceTextureListener(mSurfaceTextureListener);
        }
    }

    @Override
    public void onPause() {
        closeCamera();
        stopBackgroundThread();
        super.onPause();
    }

    private void stopBackgroundThread() {
        mBackgroundThread.quitSafely();
        try {
            mBackgroundThread.join();
            mBackgroundThread = null;
            mBackgroundHandler = null;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void startBackgroundThread() {
        mBackgroundThread = new HandlerThread("CameraBackground");
        mBackgroundThread.start();
        mBackgroundHandler = new Handler(mBackgroundThread.getLooper());
    }

    private void openCamera(){
        if(ContextCompat.checkSelfPermission(Objects.requireNonNull(getActivity()), Manifest.permission.CAMERA)!=
                PackageManager.PERMISSION_GRANTED||ContextCompat.checkSelfPermission(getActivity(),
                Manifest.permission.WRITE_EXTERNAL_STORAGE)!=PackageManager.PERMISSION_GRANTED){
            requestPermissions(new String[]{Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
            return;
        }

/*        Size largest = Collections.max(
                Arrays.asList(map.getOutputSizes(ImageFormat.JPEG),new CompareSizeByArea)
        );
        mImageReader = ImageReader.newInstance(
                largest.getWidth(),largest.getHeight(),ImageFormat.JPEG,2);
        mImageReader.setOnImageAvailableListener(
                mOnImageAvailableListener,mBackgroundHandler
        );*/

        try {
            if(!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)){
                throw new RuntimeException("Time out waiting to lock camera");
            }
            cameraManager.openCamera(mCameraId,mStateCallback,mBackgroundHandler);
        } catch (InterruptedException e) {
            throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

    }

    private void onlyCloseCamera(){
        try {
            mCameraOpenCloseLock.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (null != mCaptureSession) {
            mCaptureSession.close();
            mCaptureSession = null;
        }
        if (null != mCameraDevice) {
            mCameraDevice.close();
            mCameraDevice = null;
        }
    }

    private void closeCamera() {
        try {
            mCameraOpenCloseLock.acquire();
            if (null != mCaptureSession) {
                mCaptureSession.close();
                mCaptureSession = null;
            }
            if (null != mCameraDevice) {
                mCameraDevice.close();
                mCameraDevice = null;
            }
            if (null != mImageReader) {
                mImageReader.close();
                mImageReader = null;
            }
        } catch (InterruptedException e) {
            throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
        } finally {
            mCameraOpenCloseLock.release();
        }
    }

    public static class ErrorDialog extends DialogFragment {

        private static final String ARG_MESSAGE = "message";

        public static ErrorDialog newInstance(String message) {
            ErrorDialog dialog = new ErrorDialog();
            Bundle args = new Bundle();
            args.putString(ARG_MESSAGE, message);
            dialog.setArguments(args);
            return dialog;
        }

        @NonNull
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            final Activity activity = getActivity();
            assert getArguments() != null;
            return new AlertDialog.Builder(activity)
                    .setMessage(getArguments().getString(ARG_MESSAGE))
                    .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialogInterface, int i) {
                            assert activity != null;
                            activity.finish();
                        }
                    })
                    .create();
        }

    }

    private void saveImage(final Bitmap bitmap){
                File picturesDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
                Date date = new Date();
                try {
                    MessageDigest mdinst = MessageDigest.getInstance("MD5");
                    String en = date.toString()+ new Random().nextInt();
                    byte[] dateBytes = en.getBytes(StandardCharsets.UTF_8);
                    mdinst.update(dateBytes);
                    byte[] encry = mdinst.digest();
                    String imagePath = picturesDirectory.getAbsolutePath()+"/"+ Arrays.toString(encry) +".jpg";
                    File imageFile = new File(imagePath);
                    if(!imageFile.exists()){
                        imageFile.createNewFile();
                    }
                    FileOutputStream outputStream = new FileOutputStream(imageFile);
                    if(bitmap.compress(Bitmap.CompressFormat.JPEG,100,outputStream)){
                        outputStream.flush();
                        outputStream.close();
                    }
                    PhotoDbHelper photoDbHelper = new PhotoDbHelper(Objects.requireNonNull(getActivity()).getApplicationContext(),
                            null,null,1);
                    SQLiteDatabase sqLiteDatabase = photoDbHelper.getWritableDatabase();
                    ContentValues values = new ContentValues();
                    values.put(PhotoDbSchema.Cols.UUID, UUID.randomUUID().toString());
                    values.put(PhotoDbSchema.Cols.NAME,imagePath);
                    values.put(PhotoDbSchema.Cols.DATE,date.toString());
                    sqLiteDatabase.insert(PhotoDbSchema.NAME,null,values);
                    photoDbHelper.close();
                } catch (NoSuchAlgorithmException | IOException e) {
                    e.printStackTrace();
                }




    }


    static class CompareSizeByArea implements Comparator<Size>{

        @Override
        public int compare(Size o1, Size o2) {
            return Long.signum(o1.getWidth()*o1.getHeight()-o2.getWidth()*o2.getHeight());
        }
    }




}
